package relu.R.landscape;

import static relu.R.landscape.Landscape.birdSpecies;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Stack;

import jfm.model.BadModelException;
import jfm.model.GLPKException;
import jfm.model.Types.CropType;

import relu.R.birds.Bird;
import relu.R.birds.BirdSpecies;
import relu.R.weeds.CropGroup;
import relu.R.weeds.IFSSorter;
import relu.R.weeds.TransitionMatrix;
import relu.R.weeds.WeedSpecies;

public abstract class Landscape {
	protected List<Field> fields=new ArrayList<Field>();
	protected List<LSFarm> farms = new ArrayList<LSFarm>();
	protected List<Bird> birds=new ArrayList<Bird>();
	protected Stack<Bird> deadBirds=new Stack<Bird>();
	public static int nWinterDays=100;
	protected int isNullPoint= 0;
	/*double hr,String name_,double dr,double hedgeReq,double mc,double vc,double rs){
		homeRange=hr;
		name=name_;
		rsurv=rs;
		dailyRation=dr;
		hedgeBreedingRequirement=hedgeReq;
		meanClutchSize=mc;
		varClutchSize=vc;*/
	
	protected static ArrayList<BirdSpecies> birdSpecies=new ArrayList<BirdSpecies>();
	static {
		double homeRangeYH=1.5; // Radius of the home-range in kilometers
		double birdWeight=40;// In grams
		double hedgesYH=0.3;// Num km hedgerow per BP
		double meanreprodRateYH=2.0*0.5;// Clutch size * clutch survival *0.5 because 1 bird = 1 BP
		double variancereprodRateYH=0.5;//
		double intrinsicSurvivalRateYH=0.72;// ie without starvation
		
		BirdSpecies yh=new BirdSpecies(homeRangeYH,"Yellowhammer",birdWeight,hedgesYH,
				meanreprodRateYH,variancereprodRateYH,intrinsicSurvivalRateYH);
		// These numbers should be in kj per seed
		
		yh.addToDiet(WeedSpecies.CHEALB,0.00984);// Phil Stephens
		yh.addToDiet(WeedSpecies.POAANN,0.00553);		
		yh.addToDiet(WeedSpecies.STEMED,0.014);		
		birdSpecies.add(yh);
//		numBirds.add(yh,100);
	}
	

	


	public static Random rGen=new Random();
	
	public abstract void assignFieldsToFarmers();
	public abstract void assignFieldsToBirds();
	public abstract void addFarm(double inArea,String tmplXML,String mouXML);
	public abstract void createAndDistributeFields(int nFields);
	public abstract double createSummerBirdPopulations(int nBirds,BirdSpecies sp);
	
	/** Map .. WeedSpecies -> From Crop -> ToCrop -> TransitionMatrix */
	protected Map<WeedSpecies,HashMap<CropType,HashMap<CropType,TransitionMatrix>>> weedTransitions
	=new HashMap<WeedSpecies,HashMap<CropType,HashMap<CropType,TransitionMatrix>>>();
	

	protected static Map<String,WeedSpecies> stringToSpecies =new HashMap<String,WeedSpecies>();
	static {
		for(WeedSpecies sp:WeedSpecies.values()){
			stringToSpecies.put(sp.toString(), sp);
		}
	}
	
	public static double getMinimumImageDistance(double x1,double x2,double boxl){
		double d1=Math.abs(x2-x1);
		double d2=Math.abs(x2-x1+boxl);
		double d3=Math.abs(x1-x2+boxl);		
		return Math.min(d3,Math.min(d1, d2));
	}
	
	// Folds coordinates to be inside the box
	public static double foldCoordinate(double boxl,double coord){
		if ( coord > boxl ){
			return coord-boxl;
		} 
		if ( coord < 0 ){
			return coord+boxl;
		}
		return coord;
//			return (coord-boxl*Math.floor(coord/boxl));
	}
	

	protected abstract double doBirdWinterSurvival();

/*		int b=0;
		deadBirds.clear();
//		System.out.println(birds.size());
		for(Bird bd:birds){
//			System.out.println(b);
//			b++;
			if ( !bd.enoughFood() ){
				deadBirds.add(bd);
			}
		}
	//	System.out.println(deadBirds.size()+" died ");
		birds.removeAll(deadBirds);
//		System.out.println(birds.size());
	}*/
	
	public void solveFarmLPs(){		
		for(LSFarm farm:farms){
			try {
				farm.solve();
	//			System.out.println(farm.toString());

			} catch ( BadModelException ex){
				System.out.println("BadModel for farm");
			} catch (GLPKException ex){
				throw new Error("Solver exception");
			}
		}
	}
	
	
	public abstract double stepSummer(int nBirds);
	
	public double stepWinter(){
	//	System.out.println("Calculating winter survival");
		return	doBirdWinterSurvival();
	}

	
	/** Get the transition matrix corresponding to a species, from -> to combo. */
	protected TransitionMatrix getTransitionMatrix(WeedSpecies sp,CropType from,CropType to){
		if ( weedTransitions.containsKey(sp)){
			if ( weedTransitions.get(sp).containsKey(from)){
				if ( weedTransitions.get(sp).get(from).containsKey(to)){
					return weedTransitions.get(sp).get(from).get(to);
				}
			}
		}
		return null;
	}
	
	protected void addTransitionMatrix(TransitionMatrix tm,CropType from,CropType to,WeedSpecies sp){
		if ( !weedTransitions.containsKey(sp)){
			weedTransitions.put(sp, new HashMap<CropType,HashMap<CropType,TransitionMatrix>>());
		}
		if (!weedTransitions.get(sp).containsKey(from)){
			weedTransitions.get(sp).put(from, new HashMap<CropType,TransitionMatrix>());
		}
		if ( !weedTransitions.get(sp).get(from).containsKey(to)){
			weedTransitions.get(sp).get(from).put(to, tm);
		} else {
			throw new Error("Already have a transition matrix for "+sp+" from "+from+" to "+to);
		}
	}
	
	// Run the weed pops to equilibrium before starting a bird Sim.
	public void equilibrateWeedPopulations(int nYears){
		for(int y=0;y<nYears;y++){
			for(Field fld:fields){
				try {
					fld.rotateCropsAndPropagateWeeds();
				} catch (NullPointException ex){
					isNullPoint=1;
					return;// Don't do anything
				}
			}
		}
	}

	
	
	// This creates all the transition matrices and assigns initial weed densities to each field
	public void createWeedPopulations(IFSSorter sorter){
		for(WeedSpecies sp:WeedSpecies.values()){
		//	System.out.println(" .. for species "+sp);
			double[] densityDist=sorter.getDensityDistribution(sp.toString());
			// Assign initial population levels to all fields for this species
			for(Field fld:fields){
				fld.addWeedPopulation(sp, densityDist);
			}
			
			for(CropGroup cgf:CropGroup.values()){
//				System.out.println("   From "+cgf+" "+cgf.jfmTypes);
				for(CropGroup cgt:CropGroup.values()){
					// Winterwheat crop groups are a special case 
					if ( (cgf==CropGroup.WW1 && cgt!=CropGroup.WW2) 
							|| (cgf==CropGroup.WW2 && cgt==CropGroup.WW1)
							|| (cgf==CropGroup.WW2 && cgt==CropGroup.WW2)
							|| (cgt==CropGroup.WW2 && cgf!=CropGroup.WW1)){
						// don't add any tms for these combos
					} else {					
//						System.out.println("       to "+cgt+" "+cgt.jfmTypes);
//						double[][] transitions=sorter.getTransitions(sp.toString(), cgf.ifsCodes, cgt.ifsCodes);
						double[][] transitions=sorter.getTransitions(sp.toString(), cgt.ifsCodes);
						TransitionMatrix tm=new TransitionMatrix();
						int[] npoints = tm.create(transitions, sp.bins());
			//			if ( npoints > 3 ){ // Only add transition matrices of sufficient quality
						for(CropType ctf:cgf.jfmTypes){
							for(CropType ctt:cgt.jfmTypes){
								this.addTransitionMatrix(tm, ctf, ctt, sp);
							}
						}
				//		}
					}
				}
			}
		}
	}

	public void applySocioEconomicDistribution(SocioEconomicDistribution dist){
		for(LSFarm fm:farms){
			dist.sampleFarmer(fm);
		}
	}

	public static String oneLineCSVHeader(){
		StringBuffer buff=new StringBuffer();
/*		for(int f=0;f<nfarms;f++){			
			buff.append("NBIRDS"+f+",");
		}*/
		buff.append(LSFarm.oneLineCropHeader());
		
		
		return buff.toString();
	}
	public String oneLineCSVSummary(int farmi){
		StringBuffer buff=new StringBuffer();
		buff.append(farms.get(farmi).oneLineCropOutput());
		
		
		return buff.toString();
	}
	
}
